package com.basics.framework.webSocket;


import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.basics.api.enums.socket.MessageEnum;
import com.basics.api.response.organization.DefaultOrganizationInfoData;
import com.basics.api.response.socket.MessageData;
import com.basics.api.response.socket.OpenData;
import com.basics.api.vo.MessageVo;
import com.basics.api.vo.UserInfo;
import com.basics.channel.service.CyChannelMsgService;
import com.basics.channel.service.CyChannelService;
import com.basics.common.exception.CustomException;
import com.basics.common.utils.redis.RedisUtil;
import com.basics.framework.constants.SecurityConstants;
import com.basics.framework.utils.JwtYzUtils;
import com.basics.organization.service.CyOrganizationUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.CrossOrigin;

import javax.naming.AuthenticationException;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 该类负责监听客户端的连接、断开连接、接收消息、发送消息等操作。
 *
 * @author ww
 * @since 2024/08/29 19:50
 */
@Slf4j
@Component
@CrossOrigin(origins = "*")
@ServerEndpoint(value = "/websocket/push/v1/{token}", configurator = GetHttpSessionConfig.class)
public class ChatEndpoint {

    private static final Map<Long, ClientInfoEntity> uavWebSocketInfoMap = new ConcurrentHashMap<>();

    private static final int EXIST_TIME_HOUR = 24*30;

    @Autowired
    private static RedisUtil redisUtil;

    @Autowired
     public void setRedisUtil(RedisUtil redisUtil) {
        ChatEndpoint.redisUtil = redisUtil;
     }

     @Autowired
     private static CyChannelService cyChannelService;

    @Autowired
    private static CyOrganizationUserService cyOrganizationUserService;

    @Autowired
    private static CyChannelMsgService cyChannelMsgService;

    @Autowired
    public void setCyChannelService(CyChannelService cyChannelService) {
        ChatEndpoint.cyChannelService = cyChannelService;
    }

    @Autowired
    public void setCyOrganizationUserService(CyOrganizationUserService cyOrganizationUserService) {
        ChatEndpoint.cyOrganizationUserService = cyOrganizationUserService;
    }

    @Autowired
    public void setCyChannelMsgService(CyChannelMsgService cyChannelMsgService) {
        ChatEndpoint.cyChannelMsgService = cyChannelMsgService;
    }

    /**
     * 连接建立成功调用的方法
     *
     * @param session 第一个参数必须是session
     * @param sec
     * @param token   代表客户端的唯一标识
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig sec, @PathParam("token") String token) throws AuthenticationException{
        if (uavWebSocketInfoMap.containsKey(token)) {
            close(session);
            throw new CustomException("token已建立连接");
        }
        //把成功建立连接的会话在实体类中保存
        ClientInfoEntity entity = new ClientInfoEntity();
        Long userId = getUserIdByToken(token,session);
        entity.setUserId(userId);
        entity.setSession(session);
        //默认连接30天
        entity.setExistTime(LocalDateTime.now().plusHours(EXIST_TIME_HOUR));
        uavWebSocketInfoMap.put(userId, entity);
        //之所以获取http session 是为了获取获取httpsession中的数据 (用户名 /账号/信息)
        log.error("【WebSocket】 连接建立成功: " + token);
        DefaultOrganizationInfoData organization =  cyOrganizationUserService.getDefaultOrganization(userId);
        if(organization==null){
            close(session);
            throw new CustomException("未加入组织");
        }
        if(organization.getId()==null){
            close(session);
            throw new CustomException("未加入组织");
        }
        OpenData data = new OpenData(cyChannelService.selectHomeChannelList(organization.getId(),userId)
                ,cyChannelMsgService.getUnReadMsgNumByOrganizationId(organization.getId(),userId));
        MessageData messageData = new MessageData(MessageEnum.PUSH_CHANNEL_ALL, JSONObject.parseObject(JSONObject.toJSONString(data)),organization.getId());
        sendMessage(JSONObject.toJSONString(messageData),userId);
    }

    /**
     * 当断开连接时调用该方法
     *
     * @param session
     */
    @OnClose
    public void onClose(Session session, @PathParam("token") String token) throws AuthenticationException{
        // 找到关闭会话对应的用户 ID 并从 uavWebSocketInfoMap 中移除
        if (ObjectUtil.isNotEmpty(token) ) {
            Long userId = getUserIdByToken(token,session);
            if(uavWebSocketInfoMap.containsKey(userId)){
                uavWebSocketInfoMap.remove(userId);
                log.error("【WebSocket】 连接关闭成功: " + userId);
            }
        }
    }

    /**
     * 接受消息
     * 这是接收和处理来自用户的消息的地方。我们需要在这里处理消息逻辑
     *
     */
    @OnMessage
    public void onMessage(Session session, @PathParam("token") String token, String message) throws AuthenticationException {
        log.error("【WebSocket】 接收到消息：" + message);
        if (StringUtils.isNotEmpty(message)) {
            JSONObject msgJo = JSONObject.parseObject(message);
            String type = msgJo.getString("type");
            if (StringUtils.isNotEmpty(type) && type.equals("HEARTBEAT")) {
                this.sendMessage(message,getUserIdByToken(token,session));
            }
        }

    }

    /**
     * 处理WebSocket中发生的任何异常。可以记录这些错误或尝试恢复。
     */
    @OnError
    public void onError(Throwable error) {
        log.error("【WebSocket】 报错信息：" + error.getMessage());
        error.printStackTrace();

    }

    /**
     * 群发信息的方法
     *
     * @param message 消息
     */
    public void sendMessage(String message,Long userId) {
        log.error("【WebSocket】 发送消息：{},发送人:{}" , message,userId);
        if(uavWebSocketInfoMap.containsKey(userId)){
            uavWebSocketInfoMap.get(userId).getSession().getAsyncRemote().sendText(message);
        }
    }

    private   Long getUserIdByToken(String prefix_token,Session session)throws AuthenticationException {
        if (StringUtils.isEmpty(prefix_token)) {
            tokenExpired(session);
            throw new AuthenticationException("token 失效");
        }
        if (prefix_token != null && prefix_token.startsWith(SecurityConstants.TOKEN_PREFIX)) {
            String token = prefix_token.replace(SecurityConstants.TOKEN_PREFIX, "");
            Boolean tokenExpired = JwtYzUtils.isTokenExpired(token);
            if (tokenExpired) {
                tokenExpired(session);
                throw new AuthenticationException("token 过期");
            }
            String userId = JwtYzUtils.getUserIdFromToken(token);
            if (StringUtils.isEmpty(userId)) {
                tokenExpired(session);
                throw new AuthenticationException("token 失效");
            }
            Object obj = redisUtil.get(SecurityConstants.TOKEN_PREFIX+userId);
            log.error(">>>>>obj----------------------{}",obj);
            if(obj==null){
                tokenExpired(session);
                throw new AuthenticationException("token 失效");
            }
            if(!token.equals(obj.toString())){
                tokenExpired(session);
                throw new AuthenticationException("token 失效");
            }
            UserInfo info = JwtYzUtils.getUserFromToken(token);
            return info.getUserId();
        } else {
            tokenExpired(session);
            throw new AuthenticationException("token 错误");
        }
    }

    private void close(Session session){
        try {
            session.close();
        } catch (IOException e) {
            log.error("【WebSocket】 关闭连接: " + e.getMessage());
        }
    }
    private void tokenExpired(Session session){
        JSONObject jo = new JSONObject();
        jo.put("type","TOKEN_EXPIRE");
        session.getAsyncRemote().sendText(jo.toJSONString());
    }

}

